Understanding Custom Type Conversions

Let’s now examine a topic closely related to operator overloading: custom type conversions. To set the stage for the discussion, let’s quickly review the notion of explicit and implicit conversions between numerical data and related class types.

Recall: Numerical Conversions

In terms of the intrinsic numerical types (sbyte, int, float, etc.), an explicit conversion is required when you attempt to store a larger value in a smaller container as this may result in a loss of data. Basically, this is your way to tell the compiler, “Leave me alone, I know what I am trying to do.” Conversely, an implicit conversion happens automatically when you attempt to place a smaller type in a destination type that will not result in a loss of data:

static void Main()
{
    int a = 123;
    long b = a; // Implicit conversion from int to long
    int c = (int) b; // Explicit conversion from long to int
}

Recall: Conversions Among Related Class Types

As shown in Chapter 6, class types may be related by classical inheritance (the “is-a” relationship). In this case, the C# conversion process allows you to cast up and down the class hierarchy. For example, a derived class can always be implicitly cast to a base type. However, if you wish to store a base class type in a derived variable, you must perform an explicit cast:

// Two related class types.
class Base{}
class Derived : Base{}

class Program
{
    static void Main(string[] args)
    {    
        // Implicit cast between derived to base.
        Base myBaseType;
        myBaseType = new Derived();

        // Must explicitly cast to store base reference
        // in derived type.
        Derived myDerivedType = (Derived)myBaseType;
    }
}

This explicit cast works due to the fact that the Base and Derived classes are related by classical inheritance. However, what if you have two class types in different hierarchies with no common parent (other than System.Object) that require conversions? Given that they are not related by classical inheritance, explicit casting offers no help.

On a related note, consider value types, such as structures. Assume you have two .NET structures named Square and Rectangle. Given that structures cannot leverage classic inheritance (as they are always sealed), you have no natural way to cast between these seemingly related types.

While you could create helper methods in the structures (such as Rectangle.ToSquare()), C# lets you build custom conversion routines that allow your types to respond to the () casting operator. Therefore, if you configured the structures correctly, you would be able to use the following syntax to explicitly convert between them as follows:

// Convert a Rectangle to a Square!
Rectangle rect;
rect.Width = 3;
rect.Height = 10;
Square sq = (Square)rect;

Creating Custom Conversion Routines

Begin by creating a new Console Application named CustomConversions. C# provides two keywords, explicit and implicit, that you can use to control how your types respond during an attempted conversion. Assume you have the following class definitions:

public class Rectangle
{
    public int Width {get; set;}
    public int Height {get; set;}

    public Rectangle(int w, int h)
    {
        Width = w; Height = h;
    }
    public Rectangle(){}

    public void Draw()
    {
        for (int i = 0; i < Height; i++)
        {
            for (int j = 0; j < Width; j++)
            {
                Console.Write("*");
            }
            Console.WriteLine();
        }
    }
    
    public override string ToString()
    {
        return string.Format("[Width = {0}; Height = {1}]",
            Width, Height);
    }
}

public class Square
{
    public int Length {get; set;}
    
    public Square(int l)
    {
        Length = l;
    }

    public Square(){}
    
    public void Draw()
    {
        for (int i = 0; i < Length; i++)
        {
            for (int j = 0; j < Length; j++)
            {
                Console.Write("*");
            }
            Console.WriteLine();
        }
    }

    public override string ToString()
    { return string.Format("[Length = {0}]", Length); }

    // Rectangles can be explicitly converted
    // into Squares.
    public static explicit operator Square(Rectangle r)
    {
        Square s = new Square();
        s.Length = r.Height;
        return s;
    }
}

Notice that this iteration of the Square type defines an explicit conversion operator. Like the process of overloading an operator, conversion routines make use of the C# operator keyword, in conjunction with the explicit or implicit keyword, and must be defined as static. The incoming parameter is the entity you are converting from, while the operator type is the entity you are converting to.

In this case, the assumption is that a square (being a geometric pattern in which all sides are of equal length) can be obtained from the height of a rectangle. Thus, you are free to convert a Rectangle into a Square as follows:

static void Main(string[] args)
{
    Console.WriteLine("***** Fun with Conversions *****\n");

    // Make a Rectangle.
    Rectangle r = new Rectangle(15, 4);
    Console.WriteLine(r.ToString());
    r.Draw();

    Console.WriteLine();

    // Convert r into a Square,
    // based on the height of the Rectangle.

    Square s = (Square)r;
    Console.WriteLine(s.ToString());
    s.Draw();
    Console.ReadLine();
}

The output can be seen in Figure 12-3.

Figure 12-3

Figure 12-3. Converting a Rectangle structure to a Square structure

While it may not be all that helpful to convert a Rectangle into a Square within the same scope, assume you have a function that has been designed to take Square parameters.

// This method requires a Square type.
static void DrawSquare(Square sq)
{
    Console.WriteLine(sq.ToString());
    sq.Draw();
}

Using your explicit conversion operation on the Square type, you can now pass in Rectangle types for processing using an explicit cast:

static void Main(string[] args)
{
...
    // Convert Rectangle to Square to invoke method.
    Rectangle rect = new Rectangle(10, 5);
    DrawSquare((Square)rect);
    Console.ReadLine();
}

Additional Explicit Conversions for the Square Type

Now that you can explicitly convert Rectangles into Squares, let’s examine a few additional explicit conversions. Given that a square is symmetrical on all sides, it might be helpful to provide an explicit conversion routine that allows the caller to cast from an integer type into a Square (which, of course, will have a side length equal to the incoming integer). Likewise, what if you were to update Square such that the caller can cast from a Square into a System.Int32? Here is the calling logic:

static void Main(string[] args)
{
...
    // Converting an int to a Square.
    Square sq2 = (Square)90;
    Console.WriteLine("sq2 = {0}", sq2);

    // Converting a Square to a int.
    int side = (int)sq2;
    Console.WriteLine("Side length of sq2 = {0}", side);
    Console.ReadLine();
}

and here is the update to the Square class:

public class Square
{
...
    public static explicit operator Square(int sideLength)
    {
        Square newSq = new Square();
        newSq.Length = sideLength;
        return newSq;
    }

    public static explicit operator int (Square s)
    {return s.Length;}
}

To be honest, converting from a Square into an integer may not be the most intuitive (or useful) operation. However, it does point out a very important fact regarding custom conversion routines: the compiler does not care what you convert to or from, as long as you have written syntactically correct code.

Thus, as with overloading operators, just because you can create an explicit cast operation for a given type does not mean you should. Typically, this technique will be most helpful when you’re creating .NET structure types, given that they are unable to participate in classical inheritance (where casting comes for free).

Defining Implicit Conversion Routines

Thus far, you have created various custom explicit conversion operations. However, what about the following implicit conversion?

static void Main(string[] args)
{
...
    // Attempt to make an implicit cast?
    Square s3 = new Square();
    s3.Length = 83;
    Rectangle rect2 = s3;

    Console.ReadLine();
}

This code will not compile, given that you have not provided an implicit conversion routine for the Rectangle type. Now here is the catch: it is illegal to define explicit and implicit conversion functions on the same type if they do not differ by their return type or parameter set. This might seem like a limitation; however, the second catch is that when a type defines an implicit conversion routine, it is legal for the caller to make use of the explicit cast syntax!

Confused? To clear things up, let’s add an implicit conversion routine to the Rectangle structure using the C# implicit keyword (note that the following code assumes the width of the resulting Rectangle is computed by multiplying the side of the Square by 2):

public class Rectangle
{
...
    public static implicit operator Rectangle(Square s)
    {
        Rectangle r = new Rectangle();
        r.Height = s.Length;

        // Assume the length of the new Rectangle with
        // (Length x 2)
        r.Width = s.Length * 2;
        return r;
    }
}

With this update, you are now able to convert between types as follows:

static void Main(string[] args)
{
...
    // Implicit cast OK!
    Square s3 = new Square();
    s3.Length= 7;
    Rectangle rect2 = s3;
    Console.WriteLine("rect2 = {0}", rect2);
    DrawSquare(s3);
    
    // Explicit cast syntax still OK!
    Square s4 = new Square();
    s4.Length = 3;
    Rectangle rect3 = (Rectangle)s4;
    Console.WriteLine("rect3 = {0}", rect3);
    Console.ReadLine();
}

The Internal Representation of Custom Conversion Routines

Like overloaded operators, methods that are qualified with the implicit or explicit keywords have "special" names in terms of CIL: op_Implicit and op_Explicit, respectively (see Figure 12-4).

Figure 12-4

Figure 12-4. CIL representation of user-defined conversion routines

Note The Visual Studio 2010 Object Browser shows custom conversion operators using the “explicit operator” and "implicit operator" icons.

That wraps up our look at defining custom conversion routines. As with overloaded operators, remember that this bit of syntax is simply a shorthand notation for “normal” member functions, and in this light it is always optional. When used correctly, however, custom structures can be used more naturally, as they can be treated as true class types related by inheritance.